package com.tony.demo.ui.performance.image;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import com.puhui.lib.utils.BitmapUtils;
import com.puhui.lib.utils.DMLog;

import java.io.IOException;
import java.io.InputStream;

public class BigImageView extends View implements View.OnTouchListener {
    private static final int Vertical_Scale = 1;  //垂直缩放
    private static final int Horizontal_Scale = 2;  //横向缩放
    private static final int No_Scale = 3;  //展示全图

    private Rect mRect;
    private BitmapFactory.Options mOptions;
    private Matrix mMatrix;   //用来设置显示区域   不要在onDraw方法里面定义变量
    private GestureDetector mGestureDetector;
    private BitmapRegionDecoder mBitmapRegionDecoder; //图片区域解码器
    private Scroller mScroller;

    private int mViewWidth; //控件宽度
    private int mViewHeight; //控件高度
    private int mImageWidth;
    private int mImageHeight;
    private float mScale = 1.0f; //缩放因子

    private Bitmap mBitmap; //显示的bitmap, 可复用 通过mOptions.inBitmap = bitmap
    private int mScaleType = Vertical_Scale;
    private boolean canScale = true;  //是否可以把图片进行缩放
    /**
     * 是否调用过invalidate()方法了   在7.0之前，调用该方法会重新执行onMeasure, onLayout, onDraw方法
     * 但是在onMeasure方法中对mRect进行了初始化赋值，这样的话，在执行完onScroll()方法后就会有问题。
     */
    private boolean hasInvalidate = false;


    public BigImageView(Context context) {
        this(context, null);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
        mMatrix = new Matrix();
        mGestureDetector = new GestureDetector(context, new GestureListenerAdapter());
        mScroller = new Scroller(context);
        setOnTouchListener(this);
    }

    /**
     * 多次调用该方法会出现异常：  Caused by: java.lang.IllegalArgumentException: Problem decoding into existing bitmap
     * 解决方案: 添加mOptions = new BitmapFactory.Options();   或者   mOptions.inBitmap = null(需保证mOptions不为null);
     */
    public void setImage(InputStream is) {
        hasInvalidate = false;  //更改图片的话，则需要重新计算
        if (!mScroller.isFinished()) {
            mScroller.forceFinished(true);
        }

        mOptions = new BitmapFactory.Options();
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, mOptions);
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
        mOptions.inJustDecodeBounds = false;
        mOptions.inMutable = true;
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
//        mOptions.inSampleSize = 2;  //这个也是尺寸压缩
        try {
            releaseBitmap();
            mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //如果拿不到解码器，说明没有设置过图片
        if (null == mBitmapRegionDecoder) {
            return;
        }

        //在7.0之前，调用invalidate()方法会重新执行onMeasure, onLayout, onDraw方法
        //这里保证执行onScroll()后不会重新给mRect赋值
        if (hasInvalidate && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            return;
        }

        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();

        //如果宽高都小于控件的宽高，则根本就不需要缩放，可以直接加载整个图片
        if (mImageWidth <= mViewWidth && mImageHeight <= mViewHeight) {
            mRect.top = 0;
            mRect.left = 0;
            mRect.right = mImageWidth;
            mRect.bottom = mImageHeight;
            canScale = false;
            return;
        }

        //根据宽高来重新给mScaleType赋值
        if (mImageWidth <= mViewWidth) {  //宽度小于控件的宽度，高度大于控件高度的情况
            mScaleType = Vertical_Scale;
        } else if (mImageHeight <= mViewHeight) {  //宽度大于控件宽度，高度小于控件高度的情况
            mScaleType = Horizontal_Scale;
        } else {
            mScaleType = No_Scale;  //图片宽高都大于控件宽高的情况
        }

        //确定要加载的图片矩形区域
        mRect.top = 0;
        mRect.left = 0;
        if (mScaleType == Vertical_Scale) {
            //计算缩放因子
            mRect.right = mImageWidth;
            mScale = mViewWidth / (float) mImageWidth;
            //通过缩放因子计算得到显示区域的bottom
            mRect.bottom = (int) (mViewHeight / mScale);

            //做了个小小的测试，则测量时将mShowRect.bottom的值设置为100，在滑动前界面只能展示100的高度，在滑动后界面过程中界面依旧只能展示100的高度
            //原因：因为bitmap做了复用。虽然后来mShowRect.bottom的值正常了，但是最初解码出来的bitmap的高度只有100，在复用的时候就只能得到100高度的内容了。
            //去掉复用后，滑动的时候效果就恢复正常了
//        mShowRect.bottom = 100;

//        requestLayout();  //这个是不需要的，因为已经测量完了，而且这个控件也不需要重新布局
        } else if (mScaleType == Horizontal_Scale) {
            mRect.bottom = mImageHeight;
            mScale = mViewHeight / (float) mImageHeight;
            mRect.right = (int) (mViewWidth / mScale);
        } else {
            //这种情况就一定是图片宽高都大于控件宽高
            mRect.right = mViewWidth;
            mRect.bottom = mViewHeight;
            mScale = 1.0f;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果拿不到解码器，说明没有设置过图片
        if (null == mBitmapRegionDecoder) {
            return;
        }
        // hasInvalidate为true表示，第一次绘制，不需要复用;
        // 这样可以确保同一个bigView对象多次调用setImage方法时不会因为复用的原因出现问题
        if (hasInvalidate) {
            //解码指定区域 并得到一个bitmap
            mOptions.inBitmap = mBitmap;
        }

        mBitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);
        DMLog.e(this.getClass().getCanonicalName(), "" + BitmapUtils.getBitmapSize(mBitmap), !hasInvalidate);
        mMatrix.setScale(mScale, mScale);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

    public void releaseBitmap() {
        if (null != mBitmap) {
            mBitmap.recycle();
        }
        if (null != mBitmapRegionDecoder) {
            mBitmapRegionDecoder.recycle();
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //将触摸事件转给手势监听处理
        return mGestureDetector.onTouchEvent(event);
    }

    private class GestureListenerAdapter extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            if (!mScroller.isFinished()) {
                mScroller.forceFinished(true); //强制滑动停止
            }
            return true;
        }

        /**
         * @param e1        手势按下的事件
         * @param e2        手势滑动的事件
         * @param distanceX X轴滑动的距离
         * @param distanceY Y轴滑动的距离
         * @return
         */
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (!canScale) {
                return super.onScroll(e1, e2, distanceX, distanceY);
            }

            if (mScaleType == Vertical_Scale) { //纵向缩放的情况
                // 这里为什么把distanceY除以1.4f？？可试试去掉后看看效果。
                // 效果就是手指在滑动的时候，感觉屏幕上内容滑动的速度比手指滑动的速度快1.5到2倍的样子
                mRect.offset(0, (int) (distanceY / 1.4f));
                if (mRect.top <= 0) {
                    mRect.top = 0;
                    mRect.bottom = (int) (mViewHeight / mScale);
                }

                if (mRect.bottom >= mImageHeight) {
                    mRect.bottom = mImageHeight;
                    mRect.top = mImageHeight - (int) (mViewHeight / mScale);
                }
            } else if (mScaleType == Horizontal_Scale) {  //横向缩放的情况
                mRect.offset((int) (distanceX / 1.2f), 0);
                if (mRect.left <= 0) {
                    mRect.left = 0;
                    mRect.right = (int) (mViewWidth / mScale);
                }

                if (mRect.right >= mImageWidth) {
                    mRect.right = mImageWidth;
                    mRect.left = mImageWidth - (int) (mViewWidth / mScale);
                }
            } else if (mScaleType == No_Scale) { //不缩放的情况   根据代码的逻辑，这里一定是图片的宽高都大于控件的宽高
                mRect.offset((int) distanceX, (int) distanceY);
                if (mRect.top <= 0) {
                    mRect.top = 0;
                    mRect.bottom = mViewHeight;
                }
                if (mRect.bottom >= mImageHeight) {
                    mRect.bottom = mImageHeight;
                    mRect.top = mImageHeight - mViewHeight;
                }
                if (mRect.left <= 0) {
                    mRect.left = 0;
                    mRect.right = mViewWidth;
                }
                if (mRect.right >= mImageWidth) {
                    mRect.right = mImageWidth;
                    mRect.left = mImageWidth - mViewWidth;
                }
            }

            invalidate();
            hasInvalidate = true;
            return false;
        }

        /**
         * 处理惯性问题
         *
         * @param e1
         * @param e2
         * @param velocityX 每秒钟X轴移动的距离
         * @param velocityY
         * @return
         */
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (!canScale) {  //如果canScale为false，应当是不会执行到这里
                return super.onScroll(e1, e2, velocityX, velocityY);
            }

            if (mScaleType == Vertical_Scale) {
                //这里设置velocityY时，为什么需要取负值呢？
                //因为如果不取负值的话，如果手指是向下滑的，手指离开屏幕后界面应该是上滑的，实际效果却是向下滑动  google开发者的问题
                //将惯性滑动问题转交给Scroller来处理
                mScroller.fling(0, mRect.top, 0, (int) -velocityY,
                        0, 0, 0, mImageHeight - (int) (mViewHeight / mScale));
            } else if (mScaleType == Horizontal_Scale) {
                mScroller.fling(mRect.left, 0, (int) -velocityX, 0,
                        0, mImageWidth - (int) (mViewWidth / mScale), 0, 0);
            } else {
                /**
                 *  @param startX 从X轴的哪点开始惯性滑动
                 * @param startY 从Y轴的哪点开始惯性滑动
                 * @param velocityX X轴每秒滑动多少像素，即X轴的惯性初始速度
                 * @param velocityY Y轴每秒滑动多少像素，即Y轴的惯性初始速度
                 * @param minX X轴最小滑动到的点
                 * @param maxX X轴最大滑动到的点
                 * @param minY Y轴最小滑动到的点
                 * @param maxY Y轴最大滑动到的点
                 */
                mScroller.fling(mRect.left, mRect.top, (int) -velocityX, (int) -velocityY,
                        0, mImageWidth - mViewWidth, 0, mImageHeight - mViewHeight);
            }
            return false;
        }
    }

    /**
     * （使用onFling方法计算的结果） 如果有惯性，则继续完成滑动效果
     */
    @Override
    public void computeScroll() {
        if (mScroller.isFinished()) {
            return;
        }
        if (!canScale) {
            return;
        }

        //true 表示当前的滑动惯性还没有结束
        if (mScroller.computeScrollOffset()) {
            if (mScaleType == Vertical_Scale) {
                mRect.top = mScroller.getCurrY();
                mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            } else if (mScaleType == Horizontal_Scale) {
                mRect.left = mScroller.getCurrX();
                mRect.right = mRect.left + (int) (mViewWidth / mScale);
            } else {
                mRect.left = mScroller.getCurrX();
                mRect.top = mScroller.getCurrY();
                mRect.right = mRect.left + mViewWidth;
                mRect.bottom = mRect.top + mViewHeight;
            }
            invalidate();
        }
    }
}
